package com.github.mikephil.charting.renderer;

import android.graphics.Canvas;
import android.graphics.Paint.Align;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;

import com.github.mikephil.charting.animation.ChartAnimator;
import com.github.mikephil.charting.buffer.BarBuffer;
import com.github.mikephil.charting.buffer.HorizontalBarBuffer;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.formatter.ValueFormatter;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider;
import com.github.mikephil.charting.interfaces.dataprovider.ChartInterface;
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
import com.github.mikephil.charting.utils.MPPointF;
import com.github.mikephil.charting.utils.Transformer;
import com.github.mikephil.charting.utils.Utils;
import com.github.mikephil.charting.utils.ViewPortHandler;

import java.util.List;

/**
 * Renderer for the HorizontalBarChart.
 *
 * @author Philipp Jahoda
 */
public class HorizontalBarChartRenderer extends BarChartRenderer {

    public HorizontalBarChartRenderer(BarDataProvider chart, ChartAnimator animator,
                                      ViewPortHandler viewPortHandler) {
        super(chart, animator, viewPortHandler);

        mValuePaint.setTextAlign(Align.LEFT);
    }

    @Override
    public void initBuffers() {
        final BarData barData = mChart.getBarData();
        mBarBuffers = new HorizontalBarBuffer[barData.getDataSetCount()];

        for (int i = 0; i < mBarBuffers.length; i++) {
            final IBarDataSet set = barData.getDataSetByIndex(i);
            mBarBuffers[i] = new HorizontalBarBuffer(set.getEntryCount() * 4 * (set.isStacked() ? set.getStackSize() : 1),
                    barData.getDataSetCount(), set.isStacked());
        }
    }

    private RectF mBarShadowRectBuffer = new RectF();

    @Override
    protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
        final Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
        mBarBorderPaint.setColor(dataSet.getBarBorderColor());
        mBarBorderPaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getBarBorderWidth()));

        final boolean drawBorder = dataSet.getBarBorderWidth() > 0.f;
        final float phaseX = mAnimator.getPhaseX();
        final float phaseY = mAnimator.getPhaseY();

        // draw the bar shadow before the values
        if (mChart.isDrawBarShadowEnabled()) {
            mShadowPaint.setColor(dataSet.getBarShadowColor());

            final BarData barData = mChart.getBarData();
            final float barWidth = barData.getBarWidth();
            final float barWidthHalf = barWidth / 2.0f;
            float x;

            for (int i = 0, count = Math.min((int) (Math.ceil((float) (dataSet.getEntryCount()) * phaseX)),
                    dataSet.getEntryCount()); i < count; i++) {

                final BarEntry e = dataSet.getEntryForIndex(i);
                x = e.getX();

                mBarShadowRectBuffer.top = x - barWidthHalf;
                mBarShadowRectBuffer.bottom = x + barWidthHalf;
                trans.rectValueToPixel(mBarShadowRectBuffer);

                if (!mViewPortHandler.isInBoundsTop(mBarShadowRectBuffer.bottom)) {
                    continue;
                }
                if (!mViewPortHandler.isInBoundsBottom(mBarShadowRectBuffer.top)) {
                    break;
                }

                mBarShadowRectBuffer.left = mViewPortHandler.contentLeft();
                mBarShadowRectBuffer.right = mViewPortHandler.contentRight();
                c.drawRect(mBarShadowRectBuffer, mShadowPaint);
            }
        }

        // initialize the buffer
        final BarBuffer buffer = mBarBuffers[index];
        buffer.setPhases(phaseX, phaseY);
        buffer.setDataSet(index);
        buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency()));
        buffer.setBarWidth(mChart.getBarData().getBarWidth());
        buffer.feed(dataSet);
        trans.pointValuesToPixel(buffer.buffer);

        final boolean isSingleColor = dataSet.getColors().size() == 1;
        if (isSingleColor) {
            mRenderPaint.setColor(dataSet.getColor());
        }

        for (int j = 0; j < buffer.size(); j += 4) {
            if (!mViewPortHandler.isInBoundsTop(buffer.buffer[j + 3])) {
                break;
            }
            if (!mViewPortHandler.isInBoundsBottom(buffer.buffer[j + 1])) {
                continue;
            }

            if (!isSingleColor) {
                // Set the color for the currently drawn value. If the index
                // is out of bounds, reuse colors.
                mRenderPaint.setColor(dataSet.getColor(j / 4));
            }

            c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                    buffer.buffer[j + 3], mRenderPaint);

            if (drawBorder) {
                c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                        buffer.buffer[j + 3], mBarBorderPaint);
            }
        }
    }

    @Override
    public void drawValues(Canvas c) {
        // if values are drawn
        if (!isDrawingValuesAllowed(mChart)) {
            return;
        }
        final List<IBarDataSet> dataSets = mChart.getBarData().getDataSets();
        final float valueOffsetPlus = Utils.convertDpToPixel(5f);
        float posOffset;
        float negOffset;
        final boolean drawValueAboveBar = mChart.isDrawValueAboveBarEnabled();

        for (int i = 0; i < mChart.getBarData().getDataSetCount(); i++) {
            final IBarDataSet dataSet = dataSets.get(i);
            if (!shouldDrawValues(dataSet)) {
                continue;
            }

            final boolean isInverted = mChart.isInverted(dataSet.getAxisDependency());

            // apply the text-styling defined by the DataSet
            applyValueTextStyle(dataSet);
            final float halfTextHeight = Utils.calcTextHeight(mValuePaint, "10") / 2f;

            final ValueFormatter formatter = dataSet.getValueFormatter();
            // get the buffer
            final BarBuffer buffer = mBarBuffers[i];

            final float phaseY = mAnimator.getPhaseY();

            final MPPointF iconsOffset = MPPointF.getInstance(dataSet.getIconsOffset());
            iconsOffset.x = Utils.convertDpToPixel(iconsOffset.x);
            iconsOffset.y = Utils.convertDpToPixel(iconsOffset.y);

            // if only single values are drawn (sum)
            if (!dataSet.isStacked()) {
                for (int j = 0; j < buffer.buffer.length * mAnimator.getPhaseX(); j += 4) {
                    final float y = (buffer.buffer[j + 1] + buffer.buffer[j + 3]) / 2f;
                    if (!mViewPortHandler.isInBoundsTop(buffer.buffer[j + 1])) {
                        break;
                    }
                    if (!mViewPortHandler.isInBoundsX(buffer.buffer[j])) {
                        continue;
                    }
                    if (!mViewPortHandler.isInBoundsBottom(buffer.buffer[j + 1])) {
                        continue;
                    }

                    final BarEntry entry = dataSet.getEntryForIndex(j / 4);
                    final float val = entry.getY();
                    final String formattedValue = formatter.getBarLabel(entry);

                    // calculate the correct offset depending on the draw position of the value
                    final float valueTextWidth = Utils.calcTextWidth(mValuePaint, formattedValue);
                    posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus));
                    negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus);

                    if (isInverted) {
                        posOffset = -posOffset - valueTextWidth;
                        negOffset = -negOffset - valueTextWidth;
                    }

                    if (dataSet.isDrawValuesEnabled()) {
                        drawValue(c,
                                formattedValue,
                                buffer.buffer[j + 2] + (val >= 0 ? posOffset : negOffset),
                                y + halfTextHeight,
                                dataSet.getValueTextColor(j / 2));
                    }

                    if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
                        final Drawable icon = entry.getIcon();
                        float px = buffer.buffer[j + 2] + (val >= 0 ? posOffset : negOffset);
                        float py = y;

                        px += iconsOffset.x;
                        py += iconsOffset.y;

                        Utils.drawImage(
                                c,
                                icon,
                                (int) px,
                                (int) py,
                                icon.getIntrinsicWidth(),
                                icon.getIntrinsicHeight());
                    }
                }
                // if each value of a potential stack should be drawn
            } else {
                final Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
                int bufferIndex = 0;
                int index = 0;

                while (index < dataSet.getEntryCount() * mAnimator.getPhaseX()) {
                    final BarEntry entry = dataSet.getEntryForIndex(index);
                    final int color = dataSet.getValueTextColor(index);
                    final float[] vals = entry.getYVals();

                    // we still draw stacked bars, but there is one
                    // non-stacked
                    // in between
                    if (vals == null) {
                        if (!mViewPortHandler.isInBoundsTop(buffer.buffer[bufferIndex + 1])) {
                            break;
                        }
                        if (!mViewPortHandler.isInBoundsX(buffer.buffer[bufferIndex])) {
                            continue;
                        }
                        if (!mViewPortHandler.isInBoundsBottom(buffer.buffer[bufferIndex + 1])) {
                            continue;
                        }

                        final String formattedValue = formatter.getBarLabel(entry);
                        // calculate the correct offset depending on the draw position of the value
                        float valueTextWidth = Utils.calcTextWidth(mValuePaint, formattedValue);
                        posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus));
                        negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus);

                        if (isInverted) {
                            posOffset = -posOffset - valueTextWidth;
                            negOffset = -negOffset - valueTextWidth;
                        }

                        if (dataSet.isDrawValuesEnabled()) {
                            drawValue(c, formattedValue,
                                    buffer.buffer[bufferIndex + 2]
                                            + (entry.getY() >= 0 ? posOffset : negOffset),
                                    buffer.buffer[bufferIndex + 1] + halfTextHeight, color);
                        }

                        if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
                            final Drawable icon = entry.getIcon();
                            float px = buffer.buffer[bufferIndex + 2]
                                    + (entry.getY() >= 0 ? posOffset : negOffset);
                            float py = buffer.buffer[bufferIndex + 1];

                            px += iconsOffset.x;
                            py += iconsOffset.y;

                            Utils.drawImage(
                                    c,
                                    icon,
                                    (int) px,
                                    (int) py,
                                    icon.getIntrinsicWidth(),
                                    icon.getIntrinsicHeight());
                        }
                    } else {
                        final float[] transformed = new float[vals.length * 2];
                        float posY = 0f;
                        float negY = -entry.getNegativeSum();

                        for (int k = 0, idx = 0; k < transformed.length; k += 2, idx++) {
                            final float value = vals[idx];
                            float y;

                            if (value == 0.0f && (posY == 0.0f || negY == 0.0f)) {
                                // Take care of the situation of a 0.0 value, which overlaps a non-zero bar
                                y = value;
                            } else if (value >= 0.0f) {
                                posY += value;
                                y = posY;
                            } else {
                                y = negY;
                                negY -= value;
                            }

                            transformed[k] = y * phaseY;
                        }

                        trans.pointValuesToPixel(transformed);

                        for (int k = 0; k < transformed.length; k += 2) {
                            final float val = vals[k / 2];
                            final String formattedValue = formatter.getBarStackedLabel(val, entry);
                            // calculate the correct offset depending on the draw position of the value
                            float valueTextWidth = Utils.calcTextWidth(mValuePaint, formattedValue);
                            posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus));
                            negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus);

                            if (isInverted) {
                                posOffset = -posOffset - valueTextWidth;
                                negOffset = -negOffset - valueTextWidth;
                            }

                            final boolean drawBelow =
                                    (val == 0.0f && negY == 0.0f && posY > 0.0f) || val < 0.0f;
                            final float x = transformed[k]
                                    + (drawBelow ? negOffset : posOffset);
                            final float y = (buffer.buffer[bufferIndex + 1] + buffer.buffer[bufferIndex + 3]) / 2f;

                            if (!mViewPortHandler.isInBoundsTop(y)) {
                                break;
                            }
                            if (!mViewPortHandler.isInBoundsX(x)) {
                                continue;
                            }
                            if (!mViewPortHandler.isInBoundsBottom(y)) {
                                continue;
                            }
                            if (dataSet.isDrawValuesEnabled()) {
                                drawValue(c, formattedValue, x, y + halfTextHeight, color);
                            }

                            if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
                                final Drawable icon = entry.getIcon();
                                Utils.drawImage(
                                        c,
                                        icon,
                                        (int) (x + iconsOffset.x),
                                        (int) (y + iconsOffset.y),
                                        icon.getIntrinsicWidth(),
                                        icon.getIntrinsicHeight());
                            }
                        }
                    }

                    bufferIndex = vals == null ? bufferIndex + 4 : bufferIndex + 4 * vals.length;
                    index++;
                }
            }

            MPPointF.recycleInstance(iconsOffset);
        }
    }

    @Override
    public void drawValue(Canvas c, String valueText, float x, float y, int color) {
        mValuePaint.setColor(color);
        c.drawText(valueText, x, y, mValuePaint);
    }

    @SuppressWarnings({"UnnecessaryLocalVariable", "SuspiciousNameCombination"})
    @Override
    protected void prepareBarHighlight(float x, float y1, float y2, float barWidthHalf, Transformer trans) {
        float top = x - barWidthHalf;
        float bottom = x + barWidthHalf;
        float left = y1;
        float right = y2;
        mBarRect.set(left, top, right, bottom);
        trans.rectToPixelPhaseHorizontal(mBarRect, mAnimator.getPhaseY());
    }

    @SuppressWarnings("SuspiciousNameCombination")
    @Override
    protected void setHighlightDrawPos(Highlight high, RectF bar) {
        high.setDraw(bar.centerY(), bar.right);
    }

    @Override
    protected boolean isDrawingValuesAllowed(ChartInterface chart) {
        return chart.getData().getEntryCount() < chart.getMaxVisibleCount()
                * mViewPortHandler.getScaleY();
    }
}
